/**
 * URL: http://code.google.com/p/nagatsuki-accounting-lib/
 */
/**
 * $Id: LedgerServiceImpl.java 118 2009-07-28 07:54:26Z tanaka.akira.2006 $
 */
package com.googlecode.nagatsuki.accountinglib.service.impl;

import static com.googlecode.nagatsuki.accountinglib.constants.AccountCategoryConstants.ASSET;
import static com.googlecode.nagatsuki.accountinglib.constants.AccountCategoryConstants.EQUITY;
import static com.googlecode.nagatsuki.accountinglib.constants.AccountCategoryConstants.EXPENSE;
import static com.googlecode.nagatsuki.accountinglib.constants.AccountCategoryConstants.LIABILITY;
import static com.googlecode.nagatsuki.accountinglib.constants.AccountCategoryConstants.PL;
import static com.googlecode.nagatsuki.accountinglib.constants.AccountCategoryConstants.REVENUE;

import java.math.BigDecimal;

import org.apache.commons.lang.StringUtils;

import com.googlecode.nagatsuki.accountinglib.exception.IllegalDateNoException;
import com.googlecode.nagatsuki.accountinglib.exception.LedgerNotFoundException;
import com.googlecode.nagatsuki.accountinglib.exception.UnbalancedException;
import com.googlecode.nagatsuki.accountinglib.model.Account;
import com.googlecode.nagatsuki.accountinglib.model.journal.Entry;
import com.googlecode.nagatsuki.accountinglib.model.journal.Transaction;
import com.googlecode.nagatsuki.accountinglib.model.ledger.GeneralLedger;
import com.googlecode.nagatsuki.accountinglib.model.ledger.Ledger;
import com.googlecode.nagatsuki.accountinglib.service.LedgerService;
import com.googlecode.nagatsuki.accountinglib.service.base.AbstractBaseService;
import com.googlecode.nagatsuki.accountinglib.util.DateNoUtil;

/**
 * 勘定元帳サービス実装
 *
 * @author tanaka.akira.2006
 *
 */
public class LedgerServiceImpl extends AbstractBaseService implements
		LedgerService {

	/*
	 * (非 Javadoc)
	 *
	 * @see com.googlecode.nagatsuki.accountinglib.service.LedgerService#post(com.googlecode.nagatsuki.accountinglib.model.ledger.GeneralLedger,
	 *      com.googlecode.nagatsuki.accountinglib.model.journal.Transaction)
	 */
	@Override
	public void post(GeneralLedger generalLedger, Transaction transaction)
			throws UnbalancedException, IllegalDateNoException {
		if (generalLedger == null) {
			throw new NullPointerException("generalLedger is null.");
		}
		if (transaction == null) {
			throw new NullPointerException("transaction is null.");
		}
		if (StringUtils.isEmpty(transaction.dateNo)) {
			throw new NullPointerException("transaction's dateNo is empty.");
		}

		// 取引番号検証
		DateNoUtil.validate(transaction.dateNo);

		// １取引の貸借バランスチェック
		BigDecimal debitSum = new BigDecimal("0");
		BigDecimal creditSum = new BigDecimal("0");
		for (Entry entry : transaction.debits) {
			debitSum = debitSum.add(entry.amount);
		}
		for (Entry entry : transaction.credits) {
			creditSum = creditSum.add(entry.amount);
		}
		if (!debitSum.equals(creditSum)) {
			throw new UnbalancedException();
		}

		// １取引を転記
		// 借方
		for (Entry entry : transaction.debits) {
			post(generalLedger, entry);
		}
		// 貸方
		for (Entry entry : transaction.credits) {
			post(generalLedger, entry);
		}
	}

	private void post(GeneralLedger generalLedger, Entry entry) {

		Account account = entry.account;
		Ledger ledger = getLedger(generalLedger, account.name);

		// 勘定元帳が無い場合は新規作成。
		if (ledger == null) {
			ledger = new Ledger(new Account(account.category,
					account.subCategory, account.name));

			switch (account.category) {
			// ** 資産
			case ASSET:
				generalLedger.assetLedgers.add(ledger);
				break;
			// ** 負債
			case LIABILITY:
				generalLedger.liabLedgers.add(ledger);
				break;
			// ** 純資産
			case EQUITY:
				generalLedger.equityLedgers.add(ledger);
				break;
			// ** 収益
			case REVENUE:
				generalLedger.revenueLedgers.add(ledger);
				break;
			// ** 費用
			case EXPENSE:
				generalLedger.expenseLedgers.add(ledger);
				break;
			// ** 損益
			case PL:
				generalLedger.plLedger = ledger;
				break;
			default:
				break;
			}
		}

		//
		// 仕訳最小単位がどの勘定タイプに属するかで処理を振り分け。

		// 転記
		if (entry.debit) {
			ledger.debits.put(entry.dateNo, entry);
		} else {
			ledger.credits.put(entry.dateNo, entry);
		}

	}

	/**
	 * 既に作成済みの勘定元帳があればそれを返します。 <br>
	 * なければnullを返します。
	 *
	 * @param generalLedger
	 * @param accountName
	 * @return {@link Ledger}
	 */
	private Ledger getLedger(GeneralLedger generalLedger,
			final String accountName) {
		for (Ledger ledger : generalLedger.assetLedgers) {
			if (ledger.account.name.equals(accountName)) {
				return ledger;
			}
		}
		for (Ledger ledger : generalLedger.liabLedgers) {
			if (ledger.account.name.equals(accountName)) {
				return ledger;
			}
		}
		for (Ledger ledger : generalLedger.equityLedgers) {
			if (ledger.account.name.equals(accountName)) {
				return ledger;
			}
		}
		for (Ledger ledger : generalLedger.revenueLedgers) {
			if (ledger.account.name.equals(accountName)) {
				return ledger;
			}
		}
		for (Ledger ledger : generalLedger.expenseLedgers) {
			if (ledger.account.name.equals(accountName)) {
				return ledger;
			}
		}
		if (generalLedger.plLedger != null
				&& generalLedger.plLedger.account.name.equals(accountName)) {
			return generalLedger.plLedger;
		}
		return null;
	}

	/*
	 * (非 Javadoc)
	 *
	 * @see com.googlecode.nagatsuki.accountinglib.service.LedgerService#delete(com.googlecode.nagatsuki.accountinglib.model.ledger.GeneralLedger,
	 *      java.lang.String, java.lang.String)
	 */
	@Override
	public void delete(GeneralLedger generalLedger, final String ledgerName,
			String dateNo) throws IllegalDateNoException,
			LedgerNotFoundException {
		if (generalLedger == null) {
			throw new NullPointerException("generalLedger is null.");
		}
		if (StringUtils.isEmpty(dateNo)) {
			throw new NullPointerException("dateNo is empty.");
		}

		// 取引番号検証
		DateNoUtil.validate(dateNo);

		// 勘定元帳の有無チェック
		Ledger ledger = getLedger(generalLedger, ledgerName);
		if (ledger == null) {
			throw new LedgerNotFoundException(String.format("%s not found.",
					ledgerName));
		}

		// 総勘定元帳から削除
		ledger.debits.remove(dateNo);
		ledger.credits.remove(dateNo);
	}

	/*
	 * (非 Javadoc)
	 *
	 * @see com.googlecode.nagatsuki.accountinglib.service.LedgerService#update(com.googlecode.nagatsuki.accountinglib.model.ledger.GeneralLedger,
	 *      java.lang.String, java.lang.String,
	 *      com.googlecode.nagatsuki.accountinglib.model.journal.Transaction)
	 */
	@Override
	public void update(GeneralLedger generalLedger, final String ledgerName,
			String dateNo, Transaction transaction) throws UnbalancedException,
			IllegalDateNoException, LedgerNotFoundException {
		if (generalLedger == null) {
			throw new NullPointerException("generalLedger is null.");
		}
		if (transaction == null) {
			throw new NullPointerException("transaction is null.");
		}
		if (StringUtils.isEmpty(dateNo)) {
			throw new NullPointerException("dateNo is empty.");
		}
		// 取引番号検証
		DateNoUtil.validate(dateNo);

		// 勘定元帳の有無チェック
		Ledger ledger = getLedger(generalLedger, ledgerName);
		if (ledger == null) {
			throw new LedgerNotFoundException(String.format("%s not found.",
					ledgerName));
		}

		// 総勘定元帳を更新(対象となる取引を一旦削除してから新規入力)
		delete(generalLedger, ledgerName, dateNo);
		post(generalLedger, transaction);
	}
}
